package com.michaelbulava.TFS.vcs.internal;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.FileStatus;
import com.intellij.openapi.vcs.actions.VcsContextFactory;
import com.intellij.openapi.vcs.changes.ChangeListManagerGate;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileVisitor;
import com.intellij.vcsUtil.VcsUtil;
import com.michaelbulava.TFS.log.FileLogger;
import com.michaelbulava.TFS.vcs.TFSVcs;
import com.michaelbulava.TFS.vcs.utils.TFSFileUtil;
import com.michaelbulava.TFS.vcs.utils.TFSProgressUtil;
import com.microsoft.tfs.core.clients.versioncontrol.GetItemsOptions;
import com.microsoft.tfs.core.clients.versioncontrol.WorkspaceLocation;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.*;
import com.microsoft.tfs.core.clients.versioncontrol.specs.ItemSpec;
import com.microsoft.tfs.util.Check;
import com.sun.corba.se.spi.activation.Server;
import org.apache.commons.lang.ArrayUtils;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Created by mbulava on 5/7/2015.
 */

public class StatusProvider {
    private static final Logger log = FileLogger.getInstance(StatusProvider.class);

    //TODO look at moving this logic to a more central location
    private static Map<FilePath, ExtendedItem> getWorkspaceExtendedItem(Project project, Workspace workspace,
                                                                        ItemSpec[] itemSpecs){
        Map<FilePath, ExtendedItem> extendedItemMap = new HashMap<FilePath, ExtendedItem>();
        ExtendedItem[][] extendedItems = null;
        try{
            if (TFSVcs.getInstance(project).isWorkspaceOnline(workspace)){
                //if we are local we can go after the workspace to get extended items otherwise we need to go to the client
                if(workspace.getLocation() == WorkspaceLocation.SERVER){
                    log.info("Trying to perform lookup of extended items via workspace...");
                    extendedItems = workspace.getExtendedItems(itemSpecs, DeletedState.ANY, ItemType.ANY,
                            GetItemsOptions.combine(new GetItemsOptions[] {
                                    GetItemsOptions.INCLUDE_BRANCH_INFO,
                                    GetItemsOptions.INCLUDE_SOURCE_RENAMES,
                                    GetItemsOptions.INCLUDE_RECURSIVE_DELETES
                            }));
                } else {
                    log.info("Trying to perform lookup of extended items via VersionControlClient");
                    extendedItems = workspace.getClient().getExtendedItems(itemSpecs, DeletedState.ANY, ItemType.ANY,
                            GetItemsOptions.combine(new GetItemsOptions[]{
                                    GetItemsOptions.INCLUDE_BRANCH_INFO,
                                    GetItemsOptions.INCLUDE_SOURCE_RENAMES,
                                    GetItemsOptions.INCLUDE_RECURSIVE_DELETES
                            }));
                }
            } else if (workspace.isLocalWorkspace()) {
                extendedItems = workspace.getExtendedItems(itemSpecs, DeletedState.ANY, ItemType.ANY,
                        GetItemsOptions.LOCAL_ONLY);
            }
        } catch(Throwable tr){
            log.error("Failed to perform ExtendedItem lookup:", tr);
        }
        if (extendedItems != null && extendedItems.length > 0){
            for(int i =0; i < extendedItems.length; i++){
                for(int j = 0; j < extendedItems[i].length; j++){
                    ExtendedItem item = extendedItems[i][j];
                    FilePath itemFile = TFSFileUtil.getFilePath(workspace, item);
//                    String filePath = item.getLocalItem();
//
//                    if (filePath == null){
//                        //If the filePath it null the file most likely doesn't exist locally
//                        //So let TFS translate from Server to local path
//                        PathTranslation translatedPath = workspace
//                                .translateServerPathToLocalPath(item.getTargetServerItem());
//                        filePath = translatedPath.getTranslatedPath();
//                    }
//                    FilePath itemFile = TFSFileUtil.getFilePath(filePath, item.getItemType() == ItemType.FOLDER);
                    extendedItemMap.put(itemFile, item);
                }
            }
        }
        return extendedItemMap;
    }

    public static void visitByStatus(Workspace workspace, List<FilePath> roots, boolean recursive,
                                     ProgressIndicator indicator, Project project, ChangeListManagerGate addGate,
                                     StatusVisitor statusVisitor){
        Check.notNull(statusVisitor, "statusVisitor");
        if (workspace != null){
            ItemSpec[] itemSpecs = TFSFileUtil.getItemSpecs(project, workspace, roots, recursive, false);//roots, workspace, recursive);

            if (itemSpecs.length == 0)//This can happen it the that Module folder is the Working Folder
                return;
            //Get the Extended Items for all the roots..
            TFSProgressUtil.setProgressText(indicator, "Retrieve extended items");

            Map<FilePath, ExtendedItem> extendedItems = getWorkspaceExtendedItem(project, workspace, itemSpecs);

            //Get the workspace's pending changes
            AtomicReference<PendingChange[]> candidateChanges = new AtomicReference<>();
            //PendingSet pendingSet = null;

            TFSProgressUtil.setProgressText(indicator, "Retrieve Pending changes");
            PendingChange[] pendingChangesArray = new PendingChange[0];
            try{
                if (workspace.isLocalWorkspace()){
                    pendingChangesArray = workspace.getPendingChangesWithCandidates(itemSpecs, true, candidateChanges);
                    if (candidateChanges.get() != null && candidateChanges.get().length > 0){
                        PendingChange[] mergeCandidates = (PendingChange[])ArrayUtils.addAll(pendingChangesArray,
                                candidateChanges.get());
                        pendingChangesArray = mergeCandidates;
                    }
                } else {
                    pendingChangesArray = workspace.getPendingChanges(itemSpecs, true).getPendingChanges();
                }
            } catch (Exception e){
                RuntimeException e2 = new RuntimeException("Failed to get pending changes", e);
                log.error("Failed to get pending changes", e2);
                throw e2;
            }
            List<PendingChange> pendingChanges = new ArrayList<>();
            pendingChanges.addAll(Arrays.asList(pendingChangesArray));

            TFSProgressUtil.checkCanceled(indicator);

            for(FilePath root : roots){

                Collection<FilePath> localFiles = new HashSet<FilePath>();

                localFiles.add(root);
                if (recursive){
                    addExistingFilesRecursively(localFiles, root.getVirtualFile());
                }

                for(FilePath localFile : localFiles){
                    FileStatus internalStatus = null;
                    if (addGate != null && root.getVirtualFile() != null)
                        internalStatus = addGate.getStatus(root.getVirtualFile());

                    ExtendedItem extendedItem = null;
                    PendingChange pendingChange = null;
                    for (int i = 0; i < pendingChanges.size(); i++){
                        if (equals(localFile, pendingChanges.get(i).getLocalItem())){
                            pendingChange = pendingChanges.remove(i);
                            break;
                        }
                    }
                    if (!extendedItems.isEmpty()){
                        extendedItem = extendedItems.remove(localFile);
                    }


                    boolean localItemExists = TFSFileUtil.localItemExists(localFile);
                    if (!localItemExists && pendingChange.getChangeType().contains(ChangeType.DELETE)){
                        localFile = VcsUtil.getFilePathForDeletedFile(localFile.getPath(),
                                pendingChange.getItemType() == ItemType.FOLDER);
                    }

                    boolean isWorkingFolder = false;
                    if (localFile.isDirectory()) {
                        WorkingFolder[] workingFolders = workspace.getFolders();
                        for (int i = 0; i < workingFolders.length; i++) {
                            if (equals(localFile, workingFolders[i].getLocalItemRaw())) {
                                (new ServerStatus.WorkingFolder(localFile, workingFolders[i].getServerItem())).performVisitBy(localFile, true, statusVisitor);
                                isWorkingFolder = true;
                                break;
                            }
                        }
                    }

                    if (!isWorkingFolder){
                        if (!TFSVcs.getInstance(project).isIgnoredFile(localFile)){
                            determineServerStatus(pendingChange, extendedItem, workspace.isLocalWorkspace())
                                    .visitBy(localFile, localItemExists, statusVisitor);
                        } else {
                            ServerStatus.Ignored.INSTANCE.performVisitBy(localFile, localItemExists, statusVisitor);
                        }
                    }

//                    if (extendedItem != null) {
////                        determineServerStatus(pendingChange, extendedItem, workspace.isLocalWorkspace())
////                                .visitBy(localFile, localItemExists, statusVisitor);
//                        if (!TFSVcs.getInstance(project).isIgnoredFile(localFile))
//                            determineServerStatus(pendingChange, extendedItem, workspace.isLocalWorkspace())
//                                    .visitBy(localFile, localItemExists, statusVisitor);
//                        else
//                            ServerStatus.Ignored.INSTANCE.performVisitBy(localFile, true, statusVisitor);
//
//                    } else {
//                        boolean isWorkingFolder = false;
//                        if (localFile.isDirectory()){
//                            WorkingFolder[] workingFolders = workspace.getFolders();
//                            for(int i = 0; i < workingFolders.length; i++){
//                                if (equals(localFile, workingFolders[i].getLocalItemRaw())){
//                                    (new ServerStatus.WorkingFolder(localFile, workingFolders[i].getServerItem())).performVisitBy(localFile, true, statusVisitor);
//                                    isWorkingFolder = true;
//                                    break;
//                                }
//                            }
////                                Module module = TFSFileUtil.getModuleForFile(localFile, project);
////                                FilePath moduleDir =TFSFileUtil.getFilePath(module.getModuleFile().getParent());
////                                if (moduleDir.equals(lo))
//                        }
//                        if (!isWorkingFolder){
//                            if (!TFSVcs.getInstance(project).isIgnoredFile(localFile)){
//                                log.warn("Failed to locate extended item for [" + localFile.getPath() +"]");
//                                ServerStatus.NotVersioned.INSTANCE.performVisitBy(localFile, localItemExists, statusVisitor);
//                            } else {
//                                ServerStatus.Ignored.INSTANCE.performVisitBy(localFile, localItemExists, statusVisitor);
//                            }
//                        }
//                    }
                }
                TFSProgressUtil.checkCanceled(indicator);
            }
            if (recursive){
                for(PendingChange pendingChange : pendingChanges){
                    FilePath localFile = VcsContextFactory.SERVICE.getInstance().createFilePathOn(new File(pendingChange.getLocalItem()));
                    ExtendedItem extendedItem = extendedItems.remove(localFile);

                    if (!TFSVcs.getInstance(project).isIgnoredFile(localFile))
                        determineServerStatus(pendingChange, extendedItem, workspace.isLocalWorkspace())
                                .visitBy(localFile, TFSFileUtil.localItemExists(localFile), statusVisitor);
                    else
                        ServerStatus.Ignored.INSTANCE.performVisitBy(localFile, true, statusVisitor);
                }
                for(ExtendedItem item : extendedItems.values()){
                    //TODO look at this
                    FilePath localFile = TFSFileUtil.getFilePath(workspace, item);
                    if (item.getDeletionID() != 0 && !localFile.getIOFile().exists()){
                        continue;
                    }
                    determineServerStatus(null, item,workspace.isLocalWorkspace())
                            .visitBy(localFile, TFSFileUtil.localItemExists(localFile), statusVisitor);
                }
//                for(ExtendedItem extendedItem : extendedItems.values()){
//                    PendingChange pendingChange = pendingChanges.get(extendedItem.getItemID());
//                    if (pendingChange != null || extendedItem.getLocalItem() != null){
//                        String pathString = pendingChange != null ? pendingChange.getLocalItem() :
//                                extendedItem.getLocalItem();
//
//                        FilePath localFile = VcsContextFactory.SERVICE.getInstance().createFilePathOn(new File(pathString));
//                        if (!TFSVcs.getInstance(project).isIgnoredFile(localFile))
//                            determineServerStatus(pendingChange, extendedItem, workspace.isLocalWorkspace())
//                                    .visitBy(localFile, TFSFileUtil.localItemExists(localFile), statusVisitor);
//                        else
//                            ServerStatus.Ignored.INSTANCE.performVisitBy(localFile, true, statusVisitor);
//                    }
//                }
            }
        } else {
            log.warn("Unable to get workspace for project");
        }
    }

    public static void visitByStatus(Workspace workspace, List<FilePath> roots, boolean recursive,
                                     ProgressIndicator indicator, StatusVisitor statusVisitor, Project project) {
        visitByStatus(workspace, roots, recursive, indicator, project, null, statusVisitor);
    }


//    public static void visitDisconnectedStatus(List<FilePath> roots, boolean recursive, ProgressIndicator indicator,
//                                                StatusVisitor statusVisitor){
//        Check.notNull(statusVisitor, "statusVisitor");
//        //TFSVcs vcs = TFSVcs.getInstance(project);
//
//        for(FilePath root : roots){
//            Collection<FilePath> localFiles = new HashSet<FilePath>();
//            localFiles.add(root);
//            if (recursive){
//                addExistingFilesRecursively(localFiles, root.getVirtualFile());
//            }
//
//            for(FilePath localFile : localFiles){
//                if (vcs.isIgnoredFile(localFile))
//                    (new ServerStatus.DisconnectedIgnoredFile(localFile)).performVisitBy(localFile,
//                            localFile.getIOFile().exists(), statusVisitor);
//                if(!localFile.isDirectory()){
//                    (new ServerStatus.DisconnectedFile(localFile)).performVisitBy(localFile,
//                            localFile.getIOFile().exists(), statusVisitor);
//                }
//            }
//        }
//    }


    private static ServerStatus determineServerStatus(PendingChange pendingChange, @Nullable ExtendedItem item,
                                                      boolean localWorkspace){
        ChangeType change;
        if (item != null)
            change = item.getPendingChange();
        else if (pendingChange != null)
            change = pendingChange.getChangeType();
        else
            return new ServerStatus.UpToDate(localWorkspace);


        ChangeType IgnoreChages = ChangeType.NONE.combine(ChangeType.LOCK);
        change.remove(IgnoreChages);

        if (item != null && item.getLocalItem() == null && item.getSourceServerItem() != null){
            //return new ServerStatus.OutOfDate(item);
            //TODO this is an incoming addition that should be merged.
        }

        if ((item != null && item.getLocalItem() == null) && change.isEmpty())
            return ServerStatus.NotVersioned.INSTANCE;


        if (change.isEmpty()){
            TFSVcs.assertTrue(item.getLatestVersion() != 0);
            if (item != null && item.getLocalVersion() < item.getLatestVersion())
                return new ServerStatus.OutOfDate(item);

            return new ServerStatus.UpToDate(item, localWorkspace);
        }


        if (!change.containsAny(ChangeType.ADD)){
            if (!change.containsAny(ChangeType.combine(new ChangeType[]{ ChangeType.MERGE, ChangeType.BRANCH })) ||
                    item.getLatestVersion() != 0){
                //TODO Hmmm?
            }
        } else {
            TFSVcs.assertTrue(change.containsAny(ChangeType.ENCODING));

            if (pendingChange != null)
                return new ServerStatus.ScheduledForAddition(pendingChange, localWorkspace);
            //Make sure we have the item...
            TFSVcs.assertTrue(item.getLatestVersion() == 0);
            TFSVcs.assertTrue(item.getLocalVersion() ==  0);
            return new ServerStatus.ScheduledForAddition(item, localWorkspace);
        }

        if (change.contains(ChangeType.DELETE)){
            if (pendingChange != null)
                return new ServerStatus.ScheduledForDeletion(pendingChange, localWorkspace);

            return new ServerStatus.ScheduledForDeletion(item, localWorkspace);
        }

        if (change.containsAny(ChangeType.combine(new ChangeType[]{ ChangeType.EDIT, ChangeType.MERGE,
                                                    ChangeType.ENCODING})) &&
                !change.contains(ChangeType.RENAME)){
            TFSVcs.assertTrue(item.getLatestVersion() != 0);
            if (item.getLocalVersion() != 0){
                TFSVcs.assertTrue(item.getLocalItem() != null);
                if (pendingChange != null)
                    return new ServerStatus.CheckedOutForEdit(pendingChange, localWorkspace);

                return new ServerStatus.CheckedOutForEdit(item, localWorkspace);
            }
            return new ServerStatus.ScheduledForAddition(item, localWorkspace);
        }

        if (change.containsAny(ChangeType.combine(new ChangeType[]{ChangeType.MERGE, ChangeType.RENAME})) &&
                !change.contains(ChangeType.EDIT)){
            if (pendingChange != null)
                return new ServerStatus.Renamed(pendingChange, localWorkspace);

            return new ServerStatus.Renamed(item, localWorkspace);
        }

        if (change.containsAll(ChangeType.combine(new ChangeType[]{ChangeType.RENAME, ChangeType.EDIT}))){
            TFSVcs.assertTrue(item.getLatestVersion() != 0);
            TFSVcs.assertTrue(item.getLocalVersion() != 0);
            TFSVcs.assertTrue(item.getLocalItem() != null);
            if (pendingChange != null)
                return new ServerStatus.RenamedCheckedOut(pendingChange, localWorkspace);

            return new ServerStatus.RenamedCheckedOut(item, localWorkspace);
        }


        if (change.contains(ChangeType.UNDELETE)){
            if (pendingChange != null)
                return new ServerStatus.Undeleted(pendingChange, localWorkspace);
            return  new ServerStatus.Undeleted(item, localWorkspace);
        }

        log.error("Uncovered case for item " + (item.getLocalItem() != null ? item.getLocalItem() : item.getTargetServerItem()));
//        if (item != null)
//            log.error("Uncovered case for item " + (item.getLocalItem() != null ? item.getLocalItem() : item.getTargetServerItem()));
//        else
//            log.error("Uncovered case for pending change" + pendingChange.getLocalItem() != null ? pendingChange.getLocalItem() : pendingChange.getServerItem());

        return ServerStatus.NotVersioned.INSTANCE;
    }


    private static void addExistingFilesRecursively(final Collection<FilePath> result, VirtualFile file){
        Check.notNull(result, "result");
        if (file != null && file.exists()){
            VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor(new VirtualFileVisitor.Option[0]) {

                @Override
                public boolean visitFile(VirtualFile file) {
                    Check.notNull(file, "file");
                    result.add(TFSFileUtil.getFilePath(file));
                    return true;
                }

            });
        }
    }

    private static boolean equals(FilePath path1, String path2){
        if (path2 == null)
            return path1 == null;

        return FileUtil.filesEqual(path1.getIOFile(), new File(path2));
    }
}
